package com.cyclasoft.projects.ledom.core;

import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cyclasoft.commons.io.Path;
import com.cyclasoft.projects.ledom.configuration.ConfigurationUtil;
import com.cyclasoft.projects.ledom.configuration.LanguageConfig;
import com.cyclasoft.projects.ledom.configuration.LedomConfiguration;
import com.cyclasoft.projects.ledom.configuration.LedomConfigurationException;

public class LanguageManager
{
	/**
	 * Logger for this class
	 */
	private static final Logger logger = LoggerFactory.getLogger(LanguageManager.class);

	private List<ScriptEngineLoader> scriptLoaders = new ArrayList<ScriptEngineLoader>();

	public ScriptEngineLoader getScriptEngineLoaderByScript(String scriptName)
	{
		String ftype = Path.extensionWithoutDot(scriptName);
		for (ScriptEngineLoader loader : scriptLoaders)
		{
			if (loader.isRunnable(ftype)) return loader;
		}
		return null;
	}

	public ScriptEngineLoader getScriptEngineLoader(File file)
	{
		return getScriptEngineLoaderByScript(file.getPath());
	}

	public ScriptEngineLoader getScriptEngineLoader(String name)
	{
		for (ScriptEngineLoader loader : this.scriptLoaders)
			if (loader.getName().equals(name)) return loader;
		return null;
	}

	public List<ScriptEngineLoader> getScriptEngineLoaders()
	{
		return Collections.unmodifiableList(scriptLoaders);
	}

	public void init(File dir) throws LedomConfigurationException
	{
		logger.debug("init languages from {}", dir);
		if (!dir.exists()) throw new LedomConfigurationException("language dir not exist!");

		File[] dirs = ConfigurationUtil.listDir(dir);
		for (File f : dirs)
		{
			InputStream ins = ConfigurationUtil.loadConfigFile(f, "language", f.getName());
			if (ins != null)
			{
				logger.debug("load language configuration from {}", f);
				LanguageConfig cfg = LedomConfiguration.loadLanguageConfig(ins);

				ScriptEngineLoader loader = createScriptEngineLoader(cfg, f);
				scriptLoaders.add(loader);
			}
			else
				logger.debug("not found language configuartion files in {}, skip");
		}

		if (scriptLoaders.size() == 0)
		{
			logger.error("no language defined");
			throw new LedomConfigurationException("no language defined");
		}
	}

	private ScriptEngineLoader createScriptEngineLoader(LanguageConfig cfg, File dir)
	        throws LedomConfigurationException
	{
		ScriptEngineLoader loader;
		ClassLoader cldr = null;
		if (cfg.loader == null || cfg.loader.length() == 0)
		{
			loader = new ScriptEngineLoader();
		}
		else
		{
			try
			{
				cldr = makeClassLoader(dir, cfg.classPaths);
				Class<?> clazz = cldr.loadClass(cfg.loader);
				loader = (ScriptEngineLoader) clazz.newInstance();
			}
			catch (InstantiationException e)
			{
				logger.error("init script loader error", e);
				throw new LedomConfigurationException("init script loader error:" + cfg.loader, e);
			}
			catch (IllegalAccessException e)
			{
				logger.error("access error", e);
				throw new LedomConfigurationException("access error", e);
			}
			catch (ClassNotFoundException e)
			{
				logger.error("script loader not found", e);
				throw new LedomConfigurationException("script loader \'" + cfg.loader + "\' not found", e);
			}
		}

		loader.init(dir, cfg, cldr);
		return loader;
	}

	private ClassLoader makeClassLoader(File dir, List<String> classPaths)
	{
		ArrayList<URL> urls = new ArrayList<URL>();
		for (String path : classPaths)
		{
			File f = new File(dir, path);
			if (!f.exists())
			{
				logger.debug("class path not exist:{}, try {}", f, path);
				f = new File(path);
			}
			if (f.exists())
			{
				try
				{
					urls.add(f.toURI().toURL());
				}
				catch (MalformedURLException e)
				{
					logger.warn("error classpath: " + path, e);
				}
			}
			else
				logger.debug("class path not exist:{}, ignore", f);
		}

		URLClassLoader ldr = URLClassLoader.newInstance(urls.toArray(new URL[urls.size()]));
		return ldr;
	}

}
